/*
 * downloader.cpp
 *
 *  Created on: 2023年6月13日
 *      Author: pengzc
 */

#include "downloader.h"
#include <unistd.h>
#include <utils/Log.h>
#include <sys/stat.h>
#include <base/log.hpp>
#include <base/exception.h>
#include <base/files.hpp>
#include <base/scope_guard.hpp>

namespace http {

const char* DOWNLOADING_SUFFIX = ".downloading";

inline bool isDirectory(const std::string& filename) {
  if (filename.rfind("/") == filename.length() - 1) {
    return true;
  }
  struct stat buf;
  if (0 != stat(filename.c_str(), &buf)) {
    return false;
  }
  return S_ISDIR(buf.st_mode);
}

Downloader::Downloader() {
}

void Downloader::add(const Task& task) {
  if (task.source.empty()) {
    throwException(-1, "task.source must not be empty");
  }
  if (task.target.empty()) {
    throwException(-1, "task.target must not be empty");
  }
  if (isDirectory(task.target)) {
    throwException(-1, "task.target must not be a directory '%s'", task.target.c_str());
  }
  std::lock_guard<std::recursive_mutex> lock(list_mutex_);
  task_list_.push_back(task);
  if (!isRunning()) {
    run("Download");
  }
}

int Downloader::size() {
  std::lock_guard<std::recursive_mutex> lock(list_mutex_);
  return task_list_.size();
}

void Downloader::addListener(const TaskListener listener) {
  std::lock_guard<std::recursive_mutex> lock(list_mutex_);
  listener_set_.insert(listener);
}

void Downloader::removeListener(const TaskListener listener) {
  std::lock_guard<std::recursive_mutex> lock(list_mutex_);
  listener_set_.erase(listener);
}

Downloader::~Downloader() {
  requestExitAndWait();
}

bool Downloader::threadLoop() {
  { //检查队列
    std::lock_guard<std::recursive_mutex> lock(list_mutex_);
    if (task_list_.empty()) {
      return false;
    }
  }
  //取第一个任务
  list_mutex_.lock();
  auto task = task_list_.front();
  list_mutex_.unlock();

  if (!task.before || task.before(task)) {
    //下载任务
    auto ret = download(task);
    if (task.result) {
      task.result(task, ret);
    }
  }

  //删除任务
  {
    std::lock_guard<std::recursive_mutex> lock(list_mutex_);
    task_list_.pop_front();
  }
  return true;
}

bool Downloader::download(const Task& task) {
  try {
    auto progress = [this, &task](int64_t dltotal,
        int64_t dlnow, int64_t ultotal, int64_t ulnow) {
      if (task.progress) {
        task.progress(task, dlnow, dltotal);
      }
      {
        std::lock_guard<std::recursive_mutex> lock(list_mutex_);
        for (auto listener : listener_set_) {
          listener(task, dlnow, dltotal);
        }
      }
      return true;
    };
    download(task.source, task.target, task.retry_max, task.low_speed_limit,
             progress);
  } catch (std::exception& e) {
    LOGE_TRACE("%s", e.what());
  }
  return false;
}

Downloader& Downloader::instance() {
  static Downloader dl;
  return dl;
}

void Downloader::download(const std::string& source, const std::string& target,
    int retry, const SpeedLimit& low_flow_limit,
    const http::HTTPClient::ProgressCallback& progress) {

  int status = 0;
  auto temp_file = target + DOWNLOADING_SUFFIX;
  remove(temp_file.c_str());
  SCOPE_EXIT {
    remove(temp_file.c_str());
  };

  if (retry < 1)  {
    throwHTTPException(-1, "argument 'retry' must be >= 1");
  }

  for (int i = 0; i < retry; ++i) {
    try {
      http::HTTPClient client;
      http::HTTPRequest req("GET", source);
      const int64_t last_file_size =
          base::exists(temp_file) ? base::file_size(temp_file) : 0;
//      LOGI_TRACE("resume from %lld", last_file_size);
      if (last_file_size > 0) {
        req.setResumeFrom(last_file_size);
      }

      client.setConnectTimeout(5000);
      if (low_flow_limit.bytes > 0) {
        client.setLowSpeedLimit(low_flow_limit.bytes, low_flow_limit.seconds);
      }

      auto progressing = [progress, last_file_size](int64_t dltotal,
          int64_t dlnow, int64_t ultotal, int64_t ulnow) {
        if (!progress) {
          return true;
        }
        return progress(dltotal + last_file_size, dlnow + last_file_size,
                        ultotal, ulnow);
      };

      auto response = client.perform(req, temp_file, progressing);
      status = response.status();
      if (response.status() != 200 && response.status() != 206) {
        //状态不对，直接退出
        throwHTTPException(-1, "%d %s %s", status,
                           response.reasonText().c_str(), source.c_str());
      }
      const int64_t increment = base::file_size(temp_file) - last_file_size;
//      LOGI_TRACE("increment %lld, content-length %d", increment, response.getContentLength());
      if (response.getContentLength() >= 0
          && response.getContentLength() != increment) {
        throwHTTPException(-1, "Content-Length not match(%d / %lld) %s",
                           response.getContentLength(),
                           base::file_size(temp_file), source.c_str());
      }
      //下载完后重命名
      remove(target.c_str());
      rename(temp_file.c_str(), target.c_str());
      sync();
//      LOGD_TRACE("download %d %s", status, source.c_str());
      return;
    } catch (http::Exception& e) {
      if ((e.code() == CURLE_OPERATION_TIMEDOUT) &&
          ((i + 1) < retry)) {
        LOGD_TRACE("%s, retry again %d/%d", e.what(), i + 1, retry);
        continue;
      }
      throw;
    }
  }
}

} /* namespace http */
